﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace Silent.Parallel
{
    public static class WorkPool
    {
        private static readonly ConcurrentQueue<Action> currentActions = new ConcurrentQueue<Action>();
        private static readonly List<WorkingThread> workingThreads = new List<WorkingThread>();

        public static bool IsWorking { get; private set; }
        public static WorkProperties Properties { get; private set; }

        static WorkPool()
        {
            Properties = new WorkProperties();
            var workingThread = new WorkingThread();
            workingThread.WorkingStateChanged += WorkingThreadWorkingStateChanged;
            workingThreads.Add(workingThread);
        }

        public static void QueueAction(Action action)
        {
            AdjustWorkingThreads();
            Action workAction = Properties.UseAttempts ? () => RunActionWithAttempts(action) : action;
            currentActions.Enqueue(workAction);
            RunWorkingThreads();
        }
        public static void Pause()
        {
            IsWorking = false;
        }
        public static void Resume()
        {
            IsWorking = true;
        }
        public static void SetDefault()
        {
            Properties = WorkProperties.Default;
        }

        private static void WorkingThreadWorkingStateChanged(object sender, WorkingEventArgs e)
        {
            var workingThread = sender as WorkingThread;
            if (workingThread == null)
                return;

            if (e.State == WorkingState.Stopped)
            {
                Action action = currentActions.FirstOrDefault();
                if (action == null)
                    return;

                workingThread.Assign(action);
                workingThread.BeginExecution();
            }
        }
        private static void AdjustWorkingThreads()
        {
            if (!Properties.IsParallel || workingThreads.Count == Properties.MaxThreads)
                return;
            while (workingThreads.Count < Properties.MaxThreads)
            {
                var workingThread = new WorkingThread();
                workingThread.WorkingStateChanged += WorkingThreadWorkingStateChanged;
                workingThreads.Add(workingThread);
            }
            while (workingThreads.Count > Properties.MaxThreads)
            {
                WorkingThread workingThread = workingThreads.Last();
                workingThread.WorkingStateChanged -= WorkingThreadWorkingStateChanged;
                workingThreads.Remove(workingThread);
            }
        }
        private static void RunWorkingThreads()
        {
            WorkingThread waitingThread = workingThreads.FirstOrDefault(x => x.State == WorkingState.Waiting);
            if (waitingThread == null)
                return;

            Action result;
            currentActions.TryDequeue(out result);
            waitingThread.Assign(result);
            waitingThread.BeginExecution();
        }
        private static void RunActionWithAttempts(Action action)
        {
            int attempts = 0;
            do
            {
                attempts++;
                if (RunActionSafetely(action))
                    break;
            } while (Properties.UseAttempts && attempts < Properties.MaxAttempts);
        }
        private static bool RunActionSafetely(Action action)
        {
            try
            {
                action.Invoke();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }
    }
}